package fsmsim.kernel;

import java.awt.Dimension;
import java.awt.Point;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;


public abstract class FSM {
	ArrayList<Estado> _estados=new ArrayList<Estado>();
    ArrayList<Transicion> _trans=new ArrayList<Transicion>();
    Estado _inicio=null;
    String _alfabeto;
    String _fileName=null;
    int _ancho,_alto;
    public static final char SIMB_ESPACIO_BLANCO=' ';
    public static final char SIMB_NULO='~';
    public static final char SIMB_COMODIN='*';

    protected FSM(String alfabeto, int ancho, int alto  ){
    	setAlfabeto(alfabeto);
    	_ancho=ancho;
    	_alto=alto;
    }
   
    public  void setAlfabeto(String alfabeto) {
		// FIXME : Validar caracteres del alfabeto
    	_alfabeto=alfabeto;
	}

	public String getFileName() {
        return _fileName;
    }

	public Estado addEstado(String nombre, Point coordenada) {
		return addEstado(nombre, coordenada.x, coordenada.y);
	}
    public Estado addEstado(String nombre, int x, int y) {
        if(nombre==null) return null;
        nombre=nombre.replaceAll(" ", "");
        if(nombre.length()==0) return null;
        if(getEstado(nombre)!=null) throw new RuntimeException("Ya existe un estado con el nombre dado.");
        Estado e;
        _estados.add(e=new Estado(nombre,x,y));
        if(_inicio==null)setInicial(null);
        return e;
    }
    public boolean rmEstado(Estado estado){
        if(_estados.remove(estado)){
        	estado.rmTransiciones();
            if(_inicio==estado){
                if(_estados.size()>0){
                    setInicial(_estados.get(0));
                }
                else
                    _inicio=null;
            }
            return true;
        }else
            return false;
    }

    public ArrayList<Estado> getEstados() {
        return _estados;
    }
    public ArrayList<Transicion> getTrans(){
        return _trans;
    }
    public void setNombreEstado(Estado e, String nuevoNombre){
    	if(e.getNombre().equals(nuevoNombre)) return;
    	if(getEstado(nuevoNombre)!=null) throw new RuntimeException("El nombre dado ya esta asignado a otro estado");
    	e.setNombre(nuevoNombre);
    }
    public void setEstadoFinal(Estado e, boolean isFinal){
    	if(e!=null)
    		e.setFinal(isFinal);
    }
    public String getAlfabeto(){
    	return _alfabeto;
    }
    public void setInicial(Estado estado) {
        if(estado==null){
            if(_estados.size()>0 && _inicio==null){
                setInicial(_estados.get(0));
            }
        }else{
            if(_inicio!=null)_inicio.setInicial(false);
            _inicio=estado;
            estado.setInicial(true);
        }
    }
    public Estado getEstadoInicial(){
        return _inicio;
    }

    public void save(String archivo) throws IOException{
    	Element m;
    	Document d = new Document( m=new Element("machine") );
    	m.setAttribute("type", this.getClass().getCanonicalName());
    	m.setAttribute("width", this.getAncho()+"");
    	m.setAttribute("height", this.getAlto()+"");
    	Element a= new Element("alphabet");
    	a.setText(this.getAlfabeto());
    	a.setAttribute("null", FSM.SIMB_NULO+"");
    	a.setAttribute("empty",FSM.SIMB_ESPACIO_BLANCO+"");
    	a.setAttribute("wildcard", FSM.SIMB_COMODIN+"");
    	m.addContent(a);
    	Element es = new Element("states");
    	Element ts =new Element ("transitions");
    	for(Estado e: _estados){
    		Element x= new Element("state");
    		x.setAttribute("name", e.getNombre() );
    		x.setAttribute("x",e.getX()+"");
    		x.setAttribute("y",e.getY()+"");
    		x.setAttribute("initial", e.isInicial()+"");
    		x.setAttribute("final",e.isFinal()+"");
    		es.addContent(x);
    	}
    	m.addContent(es);
    	ArrayList<Transicion> tss= getTrans();
    	for(Transicion t :tss){
    		Element y= new Element("transition");
    		y.setAttribute("origin", t.getOrigen().getNombre());
    		y.setAttribute("destination", t.getDestino().getNombre());
    		y.setAttribute("taperead",t.getLecturaCinta()+"");
    		y.setAttribute("tapewrite", t.getEscrituraCinta()+"");
    		y.setAttribute("stackread", t.getLecturaPila()+"");
    		y.setAttribute("stackwrite", t.getEscrituraPila()+"");
    		y.setAttribute("angle",t.getAngulo()+"");
    		y.setAttribute("distance", t.getDistancia()+"");
    		ts.addContent(y);
    	}
    	m.addContent(ts);
    	
    	XMLOutputter op = new XMLOutputter();
    	OutputStream os=new  FileOutputStream(archivo);
    	op.setFormat(Format.getPrettyFormat());
    	op.output(d, os);
    	op.output(d, System.out);
    }
    public static FSM load(String file) throws Exception{
    	Document d= new SAXBuilder().build(new File(file));
    	Element e= d.getRootElement();
    	if(e.getName().equals("machine")){
    		String cs= e.getAttributeValue("type");
    		FSM r=(FSM) Class.forName(cs).newInstance();
    		Class.forName(cs).getMethod("parse", Document.class).invoke(r, d);
    		return r;
    	}else throw new RuntimeException("El archivo no contiene una maquina");
    }
    public abstract void parse(Document d); 

    public Estado getEstado(String name){
        for(Estado e:_estados){
            if(e.getNombre().equals(name)) return e;
        }
        return null;
    }
    public Dimension getDimension(){
    	return new Dimension(_ancho, _alto);
    }
    public int getAncho(){
    	return _ancho;
    }
    public int getAlto(){
    	return _alto;
    }

	public Estado getEstado(Point coordenada) {
		for(Estado e: _estados){
			if(coordenada.distance(e.getX(), e.getY())<10){
				return e;
			}
		}
		return null;
	}
	public Transicion getTransicion(Point coordenada){
		ArrayList<Transicion> trans;
		for(Estado e: _estados){
			trans= e.getTransiciones();
			for(Transicion t : trans)
				if(coordenada.distance(t.getX(), t.getY())<10){
					return t;
				}
		}
		return null;
	}
	public Object getElemento(Point coordenada){
		ArrayList<Transicion> trans;
		for(Estado e: _estados){
			trans= e.getTransiciones();
			for(Transicion t : trans){
				if(coordenada.distance(t.getX(), t.getY())<10){
					return t;
				}
			}
			if(coordenada.distance(e.getX(), e.getY())<10){
				return e;
			}
		}
		return null;
	}
	
	protected void addTransicion(Transicion transicion ){
		// FIXME : Verificar que se cumplen las reglas de la maquina.
		_trans.add(transicion);
		transicion.getOrigen().addTransicion( transicion );
	}
	
	public abstract void addTransicion(Estado origen,Estado destino,String regla);

	public void rmTransicion(Transicion transicion) {
		transicion.getOrigen().rmTransicion(transicion);
		_trans.remove(transicion);
	}

	public ArrayList<Transicion> getTransicion(Estado estado, char caracterCinta, char caracterPila) {
		ArrayList<Transicion> ts=new ArrayList<Transicion>();
		for(Transicion t : estado._transiciones){
			if(t.getLecturaCinta()==caracterCinta || (t.getLecturaCinta()==FSM.SIMB_COMODIN && ts.size()==0 ) || t.getLecturaCinta()==FSM.SIMB_NULO ){
				if(t.getLecturaPila()==caracterPila || t.getLecturaCinta()==FSM.SIMB_NULO || t.getLecturaPila()==FSM.SIMB_NULO){
					ts.add(t);
				}
			}
		}
		return ts;
	}
	
	public abstract String getTipo();

}
